package io.gitee.lwq.encrypt.spring.boot.common.encrypt.strategy.impl;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
import cn.hutool.json.JSONUtil;
import io.gitee.lwq.encrypt.spring.boot.autoconfigure.properties.EncryptProperties;
import io.gitee.lwq.encrypt.spring.boot.common.encrypt.EncryptMethodFactory;
import io.gitee.lwq.encrypt.spring.boot.common.encrypt.strategy.IEncryptStrategy;
import io.gitee.lwq.encrypt.spring.boot.util.ServletUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;


/**
 * @author lwq
 *
 */
@Component
public class RsaSignAndAesEncryptStrategyImpl implements IEncryptStrategy, InitializingBean {
    private static final Logger LOGGER = LoggerFactory.getLogger(RsaSignAndAesEncryptStrategyImpl.class);

    @Resource
    private EncryptProperties config;

    @Override
    public Object encryptBody(Object body, ServerHttpRequest request) {
        try {
            String content = JSONUtil.toJsonStr(body);
            return buildRespEncryptBody(request,content);
        } catch (Exception e) {
            LOGGER.error("Encrypt reponse error!", e);
            throw new RuntimeException(e.getMessage());
        }
    }

    @Override
    public String decryptBody(HttpHeaders headers,String content) {
        try{
            Map<String, String> param=new HashMap<>();
            param.put("appKey",ServletUtil.getHeader(headers,"appKey"));
            param.put("timestamp",ServletUtil.getHeader(headers,"timestamp"));
            param.put("version",ServletUtil.getHeader(headers,"version"));
            param.put("encryptSecret",ServletUtil.getHeader(headers,"encryptSecret"));
            param.put("encryptContent",content);
            param.put("sign",ServletUtil.getHeader(headers,"sign"));
            return decryptBody(param);
        } catch (Exception e) {
            LOGGER.error("Decrypt req error!", e);
            throw new RuntimeException(e.getMessage());
        }


    }
    @Override
    public Boolean check(){
        if (EncryptMethodFactory.EncryptMethodEnum.RSA_SIGN_AND_AES
                .name().equalsIgnoreCase(config.getMethod())) {
            return Boolean.TRUE;
        }else{
            return Boolean.FALSE;
        }
    }
    @Override
    public void afterPropertiesSet() {
        EncryptMethodFactory.INSTANCE.addEncryptStrategy(EncryptMethodFactory.EncryptMethodEnum.RSA_SIGN_AND_AES,this);
    }

    private RSA getClientRsa(){
        return SecureUtil.rsa(null,Base64.decode(config.getClientPublicKey()));
    }
    private RSA getServiceRsa(){
        return SecureUtil.rsa(Base64.decode(config.getPrivateKey()),Base64.decode(config.getPublicKey()));
    }




    private String decryptBody(Map<String, String> appSendParam) {
        String appKey = appSendParam.get("appKey");
        String timestamp = appSendParam.get("timestamp");
        String version = appSendParam.get("version");
        String encryptSecret = appSendParam.get("encryptSecret");
        String encryptContent = appSendParam.get("encryptContent");
        String sign = appSendParam.get("sign");
        if(!StrUtil.isAllNotBlank(appKey,timestamp,version,encryptSecret,encryptContent,sign)){
            throw new RuntimeException("解密参数不能为null");
        }
        // 防重放时间窗口检验
        if(System.currentTimeMillis()-Long.valueOf(timestamp)>config.getReplayWindowMs()){
            throw new RuntimeException("触发防重放拦截");
        }

        LOGGER.info("decryptBody appSendParam:{}",appSendParam.toString());

        String appAppSecret=getServiceRsa().decryptStr(encryptSecret,KeyType.PrivateKey);
        String appContent=SecureUtil.aes(Base64.decode(appAppSecret))
                .decryptStr(encryptContent);
        String sha256DecryptContent = SecureUtil.sha256(appContent);
        String signStr = StrUtil.format("{}-{}-{}-{}-{}",appKey,timestamp,version,appAppSecret,sha256DecryptContent);
        String decryptSign = getClientRsa().decryptStr(sign,KeyType.PublicKey);
        if(!Objects.equals(signStr,decryptSign)){
            LOGGER.info("signStr:{},decryptSign:{}",signStr,decryptSign);
           throw new RuntimeException("解密失败");
        }
        return appContent;
    }




    private Map<String, Object> buildRespEncryptBody(ServerHttpRequest request,String appContent) {
        String serviceKey = config.getServiceKey();
        Long timestamp = System.currentTimeMillis();
        String version =ServletUtil.getHeader(request.getHeaders(),"version");

        byte[] keyBytes = SecureUtil.generateKey(SymmetricAlgorithm.AES.getValue(), 256).getEncoded();
        String appAppSecret= Base64.encode(keyBytes);

        String encryptSecret =getClientRsa().encryptBase64(appAppSecret,KeyType.PublicKey);
        Map<String, Object> param=new HashMap<>();
        String sha256DecryptContent = SecureUtil.sha256(appContent);

        String encryptContent=SecureUtil.aes(Base64.decode(appAppSecret))
                .encryptBase64(appContent);
        String signStr = StrUtil.format("{}-{}-{}-{}-{}",serviceKey,timestamp,version,appAppSecret,sha256DecryptContent);
        LOGGER.info("buildRespEncryptBody signStr:{}",signStr);
        String sign = getServiceRsa().encryptBase64(signStr,KeyType.PrivateKey);
        param.put("serviceKey",serviceKey);
        param.put("timestamp",timestamp);
        param.put("version",version);
        param.put("encryptSecret",encryptSecret);
        param.put("encryptContent",encryptContent);
        param.put("sign",sign);
        LOGGER.info("buildRespEncryptBody param:{}",JSONUtil.toJsonStr(param) );
        return param;
    }

}
